<template>
  <PageWrapper>
    <a-card>
      <div class="contents">
        <a-row>
          <a-col :span="18"></a-col>
          <a-col :span="6">
            <a-range-picker
              v-model="selectDate"
              :showTime="true"
              format="YYYY/MM/DD HH:mm:ss"
              :ranges="timeRanges"
              @ok="selectDateOk"
            />
          </a-col>
        </a-row>
        <a-row>
          <a-col :span="10">
            <a-card>
              <a-row>
                <a-col :span="8">
                  <div class="thresholdInput">
                    <span>时延阈值：</span>
                    <a-input-number
                      id="respLatencyThresholdInput"
                      :value="respLatencyThreshold"
                      @change="respLatencyThresholdInputChange"
                      :formatter="value => `${value}ms`"
                      :parser="value => value.replace('ms', '')"
                    >
                    </a-input-number>
                  </div>
                </a-col>
                <a-col :span="8">
                  <div class="thresholdInput">
                    <span>服务端异常比例：</span>
                    <a-input-number
                      id="serverExceptionThresholdInput"
                      :value="serverExceptionThreshold"
                      @change="serverExceptionThresholdInputChange"
                      :formatter="value => `${value}%`"
                      :parser="value => value.replace('%', '')"
                      :min="1"
                      :max="100"
                    >
                    </a-input-number>
                  </div>
                </a-col>
              </a-row>
              <div id="horizontalTopo"></div>
            </a-card>
          </a-col>
          <a-col :span="10">
            <a-card>
              <a-input
                v-model="input"
                placeholder="请输入服务名"
                @input="handleInput"
                @clear="handleClear"
                style="width: 30%"
                clearable
              >
              </a-input>
              <a-table
                :columns="appListColumns"
                :data-source="tableData"
                :customRow="rowclick"
                :pagination="pagination"
                rowKey="id"
                :scroll="{y: 550}"
              >
              </a-table>
            </a-card>
          </a-col>
          <a-col :span="4">
            <a-card>
              <div id="verticalTopo"></div>
            </a-card>
          </a-col>
        </a-row>
        <a-drawer
          :title="drawerTitle"
          :visible="drawer"
          :width="drawerWidth"
          @close="handleDrawerClose"
          :destroyOnClose="true"
        >
          <appDetail
            :isShow="showAppDetail"
            :machine_id="machine_id"
            :podId="podId"
            :tgid="tgid"
            :windowWidth="drawerWidth"
          />
        </a-drawer>
      </div>
    </a-card>
  </PageWrapper>
</template>

<script>
import appDetail from './appDetail.vue'
import G6 from '@antv/g6'
import { api } from '@/api'
import { timeRanges } from './components'
import {
  getReqRate,
  getRespRate,
  getServerExceptionRate,
  getServerRespLatency,
  getClientReqLatency,
  getPodTgidByTcpMetrics,
  noData,
} from '@/views/app/req'
import dayjs from 'dayjs'
import PageWrapper from '@/components/PageWrapper.vue'

const defaultPagination = {
  current: 1,
  pageSize: 10,
  showTotal: total => `总计 ${total} 项`,
  showSizeChanger: true,
  showQuickJumper: true,
}

export default {
  name: 'PathTopo',
  components: { PageWrapper, appDetail },
  data() {
    return {
      tableData: [],
      tempData: [],
      input: '',
      direction: 'rtl',
      showAppDetail: false,
      machine_id: '',
      podId: '',
      tgid: '',
      horizontalGraphNodeData: {},
      drawerWidth: 0,
      drawer: false,
      selectNode: {},
      pagination: defaultPagination,
      appListColumns: [
        {
          title: '服务名',
          dataIndex: 'label',
          key: 'label',
        },
        {
          title: '请求速率',
          dataIndex: 'reqRate',
          key: 'reqRate',
        },
        {
          title: '响应速率',
          dataIndex: 'respRate',
          key: 'respRate',
        },
        {
          title: '服务端异常比例',
          dataIndex: 'serverExceptionRate',
          key: 'serverExceptionRate',
        },
        {
          title: '请求时延',
          dataIndex: 'reqLatency',
          key: 'reqLatency',
        },
        {
          title: '响应时延',
          dataIndex: 'respLatency',
          key: 'respLatency',
        },
      ],
      horizontalGraphIns: {},
      verticalGraphNode: {},
      verticalGraphIns: {},
      respLatencyThreshold: 100,
      serverExceptionThreshold: 5,
      nodeSize: 50,
      iconSize: 36,
      drawerTitle: '',
      selectDate: [],
      timeRanges: timeRanges,
    }
  },
  computed: {},
  created() {},
  mounted() {
    var currTimestamp = Date.now()
    var fromTimestamp = currTimestamp - 1000 * 60 * 5
    this.selectDate.push(dayjs(fromTimestamp))
    this.selectDate.push(dayjs(currTimestamp))
    this.initHorizMap()
    this.initVerticalGraph()
    this.initPage()
  },
  methods: {
    async initPage() {
      await this.getNodeData()
      this.filterAbnormalNode()
      this.initTableData()
      this.horizontalGraphIns.data(this.horizontalGraphNodeData)
      this.horizontalGraphIns.render()
    },
    selectDateOk(e) {
      this.initPage()
    },
    getIconByLabel(label, id) {
      if (label.indexOf('java') !== -1 || id.indexOf('java') !== -1) {
        return '/java.png'
      }
      if (label.indexOf('redis') !== -1 || id.indexOf('redis') !== -1) {
        return '/redis.png'
      }
      if (label.indexOf('gauss') !== -1 || id.indexOf('gauss') !== -1) {
        return '/database.png'
      }
      if (label.indexOf('host') !== -1 || id.indexOf('host') !== -1) {
        return '/vm.png'
      }
      if (label.indexOf('proc') !== -1 || id.indexOf('proc') !== -1) {
        return '/process.png'
      }
    },
    getNodeTypeById(id) {
      if (id.indexOf('_proc_') !== -1) {
        return 'proc'
      }
      if (id.indexOf('_container_') !== -1) {
        return 'container'
      }
      if (id.indexOf('_pod_') !== -1) {
        return 'pod'
      }
    },
    getObjectIdTypeById(id) {
      var index = 0

      index = id.indexOf('_proc_')
      if (index !== -1) {
        return id.substr(index + 6)
      }
      index = id.indexOf('_container_')
      if (index !== -1) {
        return id.substr(index + 11)
      }
      index = id.indexOf('_pod_')
      if (index !== -1) {
        return id.substr(index + 5)
      }
    },
    handleTableChange(pagination, filters, sorter) {
      // 存储翻页状态
      this.pagination = pagination
      this.initTableData()
    },
    initTableData() {
      this.tableData = []
      if (this.input !== '') {
        for (var node of this.horizontalGraphNodeData.nodes) {
          if (node.label.indexOf(this.input) !== -1) {
            this.tableData.push(node)
          }
        }
      } else {
        this.tableData = this.horizontalGraphNodeData.nodes
      }
    },
    async getNodeData() {
      this.horizontalGraphNodeData = []
      const currTimestamp = this.selectDate[1].valueOf()
      const fromTimestamp = this.selectDate[0].valueOf()
      var queryParam = {
        from: 0,
        size: 10000, // magic number
        query: {
          bool: {
            must: [{ range: { ts: { gte: fromTimestamp, lte: currTimestamp } } }, { match: { edge_type: 'connect' } }],
          },
        },
      }
      const [_, res] = await api.getEsData(queryParam)

      const data = res.hits.hits
      var nodes = []
      var edges = []
      var item, node
      var i = 0
      var temp
      for (i = 0; i < data.length; i++) {
        item = data[i]._source
        edges.push({ source: item.src.split('/')[1], target: item.dst.split('/')[1] })
      }

      const map = new Map()
      for (i = 0; i < data.length; i++) {
        item = data[i]._source
        node = {
          id: item.src.split('/')[1],
          label: item.src_comm,
          icon: {
            img: this.getIconByLabel(item.src_comm, item.src),
          },
          type: this.getNodeTypeById(item.src),
          objectId: this.getObjectIdTypeById(item.src),
          timestamp: this.selectDate[1].valueOf(),
        }

        if (!map.has(node.id)) {
          temp = await getPodTgidByTcpMetrics(node)
          node.machine_id = temp.machine_id
          node.tgid = temp.tgid
          node.reqRate = await getReqRate(node)
          node.serverExceptionRate = await getServerExceptionRate(node)
          node.respLatency = await getServerRespLatency(node)
          node.respRate = await getRespRate(node)
          node.reqLatency = await getClientReqLatency(node)
          map.set(node.id, node)
        }
        node = {
          id: item.dst.split('/')[1],
          label: item.dst_comm,
          icon: {
            img: this.getIconByLabel(item.dst_comm, item.dst),
          },
          type: this.getNodeTypeById(item.dst),
          objectId: this.getObjectIdTypeById(item.dst),
          timestamp: this.selectDate[1].valueOf(),
        }

        if (!map.has(node.id)) {
          temp = await getPodTgidByTcpMetrics(node)
          node.machine_id = temp.machine_id
          node.tgid = temp.tgid
          node.reqRate = await getReqRate(node)
          node.serverExceptionRate = await getServerExceptionRate(node)
          node.respLatency = await getServerRespLatency(node)
          node.respRate = await getRespRate(node)
          node.reqLatency = await getClientReqLatency(node)
          map.set(node.id, node)
        }
      }

      for (var value of map.values()) {
        nodes.push(value)
      }

      this.horizontalGraphNodeData.nodes = nodes
      this.horizontalGraphNodeData.edges = edges
    },
    async getVerticalNodeData(nodeName, nodeId) {
      var nodes = []
      nodes.push({
        id: nodeId,
        label: nodeName,
        icon: {
          img: this.getIconByLabel(nodeName, nodeId),
        },
      })
      var edges = []
      var data = await this.getSignalNodeData(nodeName, nodeId)
      for (var item of data.nodes) {
        nodes.push(item)
      }
      for (var edge of data.edges) {
        edges.push(edge)
      }
      this.verticalGraphNode.nodes = nodes
      this.verticalGraphNode.edges = edges
    },
    async getSignalNodeData(nodeName, nodeId) {
      // 获取单节点的垂直拓扑关系
      // 节点类型： proc container pod
      const currTimestamp = Date.now()
      const fromTimestamp = currTimestamp - 1000 * 60 * this.queryMinutes
      var queryParam = {
        query: {
          bool: {
            must: [
              {
                range: {
                  ts: { gte: fromTimestamp, lte: currTimestamp },
                },
              },
              {
                terms: {
                  edge_type: ['belongs_to', 'runs_on'],
                },
              },
              {
                match: {
                  src: nodeId,
                },
              },
            ],
          },
        },
      }

      var nodeNameList = nodeName.split('-')

      for (let node of nodeNameList) {
        queryParam.query.bool.must.push({
          match: { src_comm: node },
        })
      }

      var map = new Map()

      var nodes = []
      var edges = []

      const [_, res] = await api.getEsData(queryParam)
      const data = res.hits.hits
      var i = 0
      var item, node
      for (i = 0; i < data.length; i++) {
        item = data[i]._source
      }

      for (i = 0; i < data.length; i++) {
        item = data[i]._source
        var dstId = item.dst

        // filter proc belongs_to appinstance relations
        if (dstId.indexOf('appinstance') !== -1) {
          continue
        }

        var hostIndex = dstId.indexOf('_host_')
        if (hostIndex !== -1) {
          dstId = item.dst.substr(0, hostIndex + 5)
        }
        node = {
          id: dstId.split('/')[1],
          label: item.dst_comm,
          icon: {
            img: this.getIconByLabel(item.dst_comm, dstId.split('/')[1]),
          },
        }
        if (!map.has(node.id)) {
          map.set(node.id, node)
        }
        edges.push({source: item.src.split('/')[1], target: dstId.split('/')[1]})
      }

      for (var value of map.values()) {
        nodes.push(value)
      }
      return { nodes: nodes, edges: edges }
    },
    initHorizMap() {
      const container = document.getElementById('horizontalTopo')
      const contextMenu = new G6.Menu({
        getContent(evt) {
          return `
                <ul style="padding-left: 10px">
                <li style="padding-bottom: 10px"><a>查看应用详情</a></li>
                <li><a>查看集群诊断</a></li>
                </ul>`
        },
        handleMenuClick: (target, item) => {
          const targetStr = JSON.stringify(target.outerHTML)
          if (item != null) {
            if (targetStr.indexOf('查看应用详情') !== -1) {
              this.drawerTitle = `${item._cfg.model.label} 节点详情`
              this.drawer = true
              this.drawerWidth = window.innerWidth * 0.8
              this.selectNode = item._cfg
              this.showAppDetail = true
              this.podId = this.selectNode.model.objectId
              this.machine_id = this.selectNode.model.machine_id
              this.tgid = this.selectNode.model.tgid
            } else if (targetStr.indexOf('查看集群诊断') !== -1) {
              this.$router.push({ path: '/app/clusterDiagnosis', query: { id: item._cfg.model.objectId } })
            }
          }
        },
        offsetX: 10,
        offsetY: 0,
        itemTypes: ['node', 'edge', 'canvas'],
      })
      const minimap = new G6.Minimap({
        size: [100, 100],
      })

      const tooltip = new G6.Tooltip({
        offsetX: 10,
        offsetY: 10,
        fixToNode: [1, 0.5],
        itemTypes: ['node'],
        getContent: e => {
          const outDiv = document.createElement('div')
          outDiv.style.width = 'fit-content'
          outDiv.style.height = 'fit-content'
          const model = e.item.getModel()
          outDiv.innerHTML = `
                ${model.label}
                <table>
                    <tr>
                        <th>指标名</th>
                        <th>数据</th>
                    </tr>
                    <tr>
                        <td>请求速率</td>
                        <td>${model.reqRate}</td>
                    </tr>
                    <tr>
                        <td>响应速率</td>
                        <td>${model.respRate}</td>
                    </tr>
                    <tr>
                        <td>服务端异常比例</td>
                        <td>${model.serverExceptionRate}</td>
                    </tr>
                    <tr>
                        <td>请求时延</td>
                        <td>${model.reqLatency}</td>
                    </tr>
                    <tr>
                        <td>响应时延</td>
                        <td>${model.respLatency}</td>
                    </tr>
                </table>
                `
          return outDiv
        },
      })
      const containerHeight = container.scrollHeight | 555
      const containerWidth = container.scrollWidth
      const graph = new G6.Graph({
        container: 'horizontalTopo',
        width: containerWidth,
        height: containerHeight,
        plugins: [contextMenu, minimap, tooltip],
        layout: {
          type: 'dagre',
          preventOverlap: true,
          nodeSize: this.nodeSize,
          rankdir: 'LR',
        },
        modes: {
          default: [
            'drag-node',
            {
              type: 'drag-canvas',
              enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape
            },
            {
              type: 'zoom-canvas',
              enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape
            },
          ],
        },
        defaultNode: {
          type: 'circle',
          size: [this.nodeSize],
          labelCfg: {
            position: 'bottom',
          },
          icon: {
            show: true,
            width: this.iconSize,
            height: this.iconSize,
          },
        },
        defaultEdge: {
          style: {
            stroke: 'grey',
            endArrow: {
              path: G6.Arrow.triangle(5, 10, 10),
              fill: 'grey',
            },
          },
        },
        nodeStateStyles: {
          hover: {
            cursor: 'pointer',
          },
          highlight: {
            opacity: 1,
          },
          dark: {
            opacity: 0.2,
          },
        },
        edgeStateStyles: {
          highlight: {
            stroke: '#999',
          },
        },
        fitCenter: true,
      })

      graph.on('render:done', () => {
        graph.centerPoint()
      })

      graph.on('node:click', async e => {
        const nodeItem = e.item // 获取被点击的节点元素对象
        this.selectNode = nodeItem._cfg
        this.verticalGraphNode = {}
        await this.getVerticalNodeData(this.selectNode.model.label, this.selectNode.id)
        this.verticalGraphIns.data(this.verticalGraphNode)
        this.verticalGraphIns.render()
      })

      graph.on('node:mouseenter', function (e) {
        const item = e.item
        graph.setAutoPaint(false)
        graph.getNodes().forEach(function (node) {
          graph.clearItemStates(node)
          graph.setItemState(node, 'dark', true)
        })
        graph.setItemState(item, 'dark', false)
        graph.setItemState(item, 'highlight', true)
        graph.getEdges().forEach(function (edge) {
          if (edge.getSource() === item) {
            graph.setItemState(edge.getTarget(), 'dark', false)
            graph.setItemState(edge.getTarget(), 'highlight', true)
            graph.setItemState(edge, 'highlight', true)
            edge.toFront()
          } else if (edge.getTarget() === item) {
            graph.setItemState(edge.getSource(), 'dark', false)
            graph.setItemState(edge.getSource(), 'highlight', true)
            graph.setItemState(edge, 'highlight', true)
            edge.toFront()
          } else {
            graph.setItemState(edge, 'highlight', false)
          }
        })
        graph.paint()
        graph.setAutoPaint(true)
      })
      function clearAllStats() {
        graph.setAutoPaint(false)
        graph.getNodes().forEach(function (node) {
          graph.clearItemStates(node)
        })
        graph.getEdges().forEach(function (edge) {
          graph.clearItemStates(edge)
        })
        graph.paint()
        graph.setAutoPaint(true)
      }
      graph.on('node:mouseleave', clearAllStats)
      graph.on('canvas:click', clearAllStats)
      this.horizontalGraphIns = graph
    },
    rowclick(record, index) {
      return {
        onClick: () => {
            console.log(record)
            const node = this.horizontalGraphIns.findById(record.id)
            this.horizontalGraphIns.focusItem(node, true, { easing: 'easeCubic', duration: 500 })

        },
      }
    },
    handleInput() {
      this.initTableData()
    },
    handleClear() {
      this.initTableData()
    },
    handleDrawerClose() {
      this.drawer = false
      this.appDetail = false
    },
    initVerticalGraph() {
      const container = document.getElementById('verticalTopo')
      const containerHeight = container.scrollHeight | 595
      const containerWidth = container.scrollWidth
      const minimap = new G6.Minimap({
        size: [150, 100],
      })
      this.verticalGraphIns = new G6.Graph({
        container: 'verticalTopo',
        width: containerWidth,
        height: containerHeight,
        plugins: [minimap],
        layout: {
          type: 'dagre',
          preventOverlap: true,
          nodeSize: 50,
        },
        modes: {
          default: [
            'drag-node',
            {
              type: 'drag-canvas',
              enableOptimize: true,
            },
            {
              type: 'zoom-canvas',
              enableOptimize: true,
            },
          ],
        },
        defaultNode: {
          type: 'circle',
          size: [50],
          labelCfg: {
            position: 'bottom',
          },
          icon: {
            show: true,
            width: 36,
            height: 36,
          },
        },
        defaultEdge: {
          style: {
            stroke: 'black',
            endArrow: {
              path: G6.Arrow.triangle(5, 10, 10),
              fill: 'black',
            },
          },
        },
        nodeStateStyles: {
          hover: {
            cursor: 'pointer',
          },
          highlight: {
            opacity: 2,
          },
          dark: {
            opacity: 0.2,
          },
        },
        edgeStateStyles: {
          highlight: {
            stroke: 'black',
          },
        },
        fitCenter: true,
      })

      this.verticalGraphIns.on('render:done', () => {
        this.verticalGraphIns.centerPoint()
      })
    },
    setNodeAbnormal(node) {
      node.style = {
        stroke: 'red',
        shadowColor: 'red',
        shadowBlur: this.iconSize / 2,
      }
      node.labelCfg = {
        style: {
          fill: 'red',
        },
      }
    },
    filterAbnormalNode() {
      for (var node of this.horizontalGraphNodeData.nodes) {
        if (
          node.serverExceptionRate !== noData &&
          node.serverExceptionRate.split(' ')[0] >= this.serverExceptionThreshold
        ) {
          this.setNodeAbnormal(node)
          continue
        }

        if (node.respLatency !== noData && node.respLatency.split(' ')[0] >= this.respLatencyThreshold) {
          this.setNodeAbnormal(node)
          continue
        }

        if (node.reqLatency !== noData && node.reqLatency.split(' ')[0] >= this.respLatencyThreshold) {
          this.setNodeAbnormal(node)
          continue
        }
      }
    },
    async respLatencyThresholdInputChange(e) {
      this.respLatencyThreshold = e
      await this.getNodeData()
      this.filterAbnormalNode()
      this.horizontalGraphIns.data(this.horizontalGraphNodeData)
      this.horizontalGraphIns.render()
      this.initTableData()
    },
    async serverExceptionThresholdInputChange(e) {
      this.serverExceptionThreshold = e
      await this.getNodeData()
      this.filterAbnormalNode()
      this.horizontalGraphIns.data(this.horizontalGraphNodeData)
      this.horizontalGraphIns.render()
      this.initTableData()
    },
  },
}
</script>

<style scoped lang="less">
.thresholdInput {
  width: 250px;
}
</style>
